﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Web.Hosting;
using System.Reflection;

namespace MVCPlugin.Utility
{
    public enum ReferenceType
    {
        Library,
        Project
    }

    public class ProjectFileDescriptor
    {
        public string AssemblyName { get; set; }
        public string TargetFrameworkVersion { get; set; }
        public string OutputPath { get; set; }
        public string OutputAbsolutePath { get; set; }
        public string OutputType { get; set; }
        public IEnumerable<string> SourceFilenames { get; set; }
        public IEnumerable<ReferenceDescriptor> References { get; set; }
    }

    public class ReferenceDescriptor
    {
        public string SimpleName { get; set; }
        public string FullName { get; set; }
        public string Path { get; set; }
        public ReferenceType ReferenceType { get; set; }
    }

    public static class ProjectFileParser
    {
        private static List<string> s_LocalLibList = new List<string>
        {
            "Antlr3.Runtime", "Autofac", "Autofac.Integration.Mvc", "Microsoft.Web.Infrastructure",
            "Microsoft.Web.Mvc.FixedDisplayModes", "Newtonsoft.Json", "RazorEngine", "RouteDebugger",
            "System.Net.Http", "System.Net.Http.Formatting", "System.Net.Http.WebRequest", "System.Web.Helpers",
            "System.Web.Http", "System.Web.Http.WebHost", "System.Web.Mvc", "System.Web.Optimization",
            "System.Web.Razor", "System.Web.WebPages.Deployment", "System.Web.WebPages",
            "System.Web.WebPages.Razor", "WebGrease"
        };

        public static ProjectFileDescriptor Parse(string projectFilePath)
        {
            if (projectFilePath.Length > 0 && projectFilePath[0] == '~')
            {
                projectFilePath = HostingEnvironment.MapPath(projectFilePath);
            }
            else
            {
                string r = Path.GetPathRoot(projectFilePath);
                if (string.IsNullOrWhiteSpace(r))
                {
                    projectFilePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, projectFilePath);
                }
            }
            if (File.Exists(projectFilePath) == false)
            {
                throw new FileNotFoundException("The file dosen't exist.", projectFilePath);
            }
            var document = XDocument.Load(projectFilePath);
            string output = GetOutputPath(document);
            return new ProjectFileDescriptor
            {
                AssemblyName = GetAssemblyName(document),
                SourceFilenames = GetSourceFilenames(document).ToArray(),
                References = GetReferenceDescriptorArray(GetReferences(document)),
                TargetFrameworkVersion = GetTargetFrameworkVersion(document),
                OutputPath = output,
                OutputAbsolutePath = Path.Combine(Path.GetDirectoryName(projectFilePath), output),
                OutputType = GetOutputType(document)
            };
        }

        private static ReferenceDescriptor[] GetReferenceDescriptorArray(IEnumerable<ReferenceDescriptor> references)
        {
            List<ReferenceDescriptor> list = new List<ReferenceDescriptor>();
            foreach (var refer in references)
            {
                if (s_LocalLibList.Contains(refer.FullName))
                {
                    string p = @"..\..\bin\" + refer.FullName + ".dll";
                    if (File.Exists(p) == false)
                    {
                        p = @"..\..\" + Application.BinFolderName + @"\" + refer.FullName + ".dll";
                    }
                    list.Add(new ReferenceDescriptor
                    {
                        FullName = refer.FullName,
                        Path = p,
                        ReferenceType = ReferenceType.Library,
                        SimpleName = refer.FullName
                    });
                    continue;                    
                }
                var tmp = s_LocalLibList.Find(x => refer.FullName.StartsWith(x + ", Version="));
                if (tmp != null)
                {
                    string p = @"..\..\bin\" + tmp + ".dll";
                    if (File.Exists(p) == false)
                    {
                        p = @"..\..\" + Application.BinFolderName + @"\" + tmp + ".dll";
                    }
                    list.Add(new ReferenceDescriptor
                    {
                        FullName = refer.FullName,
                        Path = p,
                        ReferenceType = ReferenceType.Library,
                        SimpleName = tmp
                    });
                    continue;
                }
                list.Add(refer);
            }
            return list.ToArray();
        }

        private static string GetAssemblyName(XDocument document)
        {
            return document
                .Elements(ns("Project"))
                .Elements(ns("PropertyGroup"))
                .Elements(ns("AssemblyName"))
                .Single()
                .Value;
        }

        private static string GetTargetFrameworkVersion(XDocument document)
        {
            return document
                .Elements(ns("Project"))
                .Elements(ns("PropertyGroup"))
                .Elements(ns("TargetFrameworkVersion"))
                .Single()
                .Value;
        }

        private static string GetOutputPath(XDocument document)
        {
            return document
                .Elements(ns("Project"))
                .Elements(ns("PropertyGroup"))
                .Elements(ns("OutputPath"))
                .First()
                .Value;
        }

        private static string GetOutputType(XDocument document)
        {
            return document
                .Elements(ns("Project"))
                .Elements(ns("PropertyGroup"))
                .Elements(ns("OutputType"))
                .Single()
                .Value;
        }

        private static string ExtractAssemblyName(string value)
        {
            int index = value.IndexOf(',');
            return index < 0 ? value : value.Substring(0, index);
        }

        private static IEnumerable<string> GetSourceFilenames(XDocument document)
        {
            return document
                .Elements(ns("Project"))
                .Elements(ns("ItemGroup"))
                .Elements(ns("Compile"))
                .Attributes("Include")
                .Select(c => c.Value);
        }

        private static IEnumerable<ReferenceDescriptor> GetReferences(XDocument document)
        {
            var assemblyReferences = document
                .Elements(ns("Project"))
                .Elements(ns("ItemGroup"))
                .Elements(ns("Reference"))
                .Where(c => c.Attribute("Include") != null)
                .Select(c =>
                {
                    string path = null;
                    XElement attribute = c.Elements(ns("HintPath")).FirstOrDefault();
                    if (attribute != null)
                    {
                        path = attribute.Value;
                    }

                    return new ReferenceDescriptor
                    {
                        SimpleName = ExtractAssemblyName(c.Attribute("Include").Value),
                        FullName = c.Attribute("Include").Value,
                        Path = path,
                        ReferenceType = ReferenceType.Library
                    };
                });

            var projectReferences = document
                .Elements(ns("Project"))
                .Elements(ns("ItemGroup"))
                .Elements(ns("ProjectReference"))
                .Attributes("Include")
                .Select(c => new ReferenceDescriptor
                {
                    SimpleName = Path.GetFileNameWithoutExtension(c.Value),
                    FullName = Path.GetFileNameWithoutExtension(c.Value),
                    Path = c.Value,
                    ReferenceType = ReferenceType.Project
                });

            return assemblyReferences.Union(projectReferences);
        }

        private static XName ns(string name)
        {
            return XName.Get(name, "http://schemas.microsoft.com/developer/msbuild/2003");
        }
    }
}
